package com.seahorse.youliao.config;

import com.seahorse.youliao.security.JwtAuthenticationProvider;
import com.seahorse.youliao.security.JwtLoginFilter;
import com.seahorse.youliao.security.JwtTokenAuthenticationFilter;
import com.seahorse.youliao.security.UserDetailsServiceImpl;
import com.seahorse.youliao.security.handler.*;
import com.seahorse.youliao.security.sms.SmsCodeAuthenticationProvider;
import com.seahorse.youliao.security.sms.SmsCodeLoginFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;

import javax.annotation.Resource;

/**
 * @ProjectName: youliao
 * @Package: com.seahorse.youliao.config
 * @ClassName: WebSecurityConfig
 * @Description: 安全配置类
 * 下面这个配置类是Spring Security的关键配置。
 * <p>
 * 在这个配置类中，我们主要做了以下几个配置：
 * <p>
 * 1. 访问路径URL的授权策略，如登录、Swagger访问免登录认证等
 * <p>
 * 2. 指定了登录认证流程过滤器 JwtLoginFilter，由它来触发登录认证
 * <p>
 * 3. 指定了自定义身份认证组件 JwtAuthenticationProvider，并注入 UserDetailsService
 * <p>
 * 4. 指定了访问控制过滤器 JwtTokenAuthenticationFilter，在授权时解析令牌和设置登录状态
 * <p>
 * 5. 指定了退出登录处理器，因为是前后端分离，防止内置的登录处理器在后台进行跳转
 * @author:songqiang
 * @Date:2020-01-10 9:39
 **/

/**
 * @EnableWebSecurity 作用 ：
 * 1: 加载了WebSecurityConfiguration配置类, 配置安全认证策略。在这个配置类中,
 * 注入了一个非常重要的bean, bean的name为: springSecurityFilterChain，
 * 这是Spring Secuity的核心过滤器, 这是请求的认证入口。
 * 2: 加载了AuthenticationConfiguration, 配置了认证信息。
 *  这个类是来配置认证相关的核心类, 这个类的主要作用是,
 *  向spring容器中注入AuthenticationManagerBuilder, AuthenticationManagerBuilder其实是使用了建造者模式,
 *  他能建造AuthenticationManager, 这个类前面提过,是身份认证的入口。
 *
 *  @EnableGlobalMethodSecurity详解：
 *  @EnableGlobalMethodSecurity(securedEnabled=true) 开启@Secured 注解过滤权限
 *  @EnableGlobalMethodSecurity(jsr250Enabled=true)开启@RolesAllowed 注解过滤权限
 *  @EnableGlobalMethodSecurity(prePostEnabled=true) 使用表达式时间方法级别的安全性
 *   4个注解可用：
 *          @PreAuthorize 在方法调用之前, 基于表达式的计算结果来限制对方法的访问
 *          @PostAuthorize 允许方法调用, 但是如果表达式计算结果为false, 将抛出一个安全性异常
 *          @PostFilter 允许方法调用, 但必须按照表达式来过滤方法的结果
 *          @PreFilter 允许方法调用, 但必须在进入方法之前过滤输入值
 *
 * 权限资源 SecurityMetadataSource
 * 权限决策 AccessDecisionManager
 *
 * 针对于url 和 方法注解权限控制：WebSecurityConfigurerAdapter 适配来实现
 * FilterSecurityInterceptor 资源控制
 * MethodSecurityInterceptor 方法注解权限控制
 *
 * 更多springSecurity 相关可以查找相关文章 深入源码了解  深入越深 坑越大 越陷越深 最后你会发现柳暗花明又一村
 * 不知觉中就进步了  要进阶 的适应慢慢看源码
 *  @author:songqiang
 *  @Date:2020-01-14 9:39
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private MyAuthenticationEntryPoint myAuthenticationEntryPoint;

    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Autowired
    private MyLogoutSuccessHandler myLogoutSuccessHandler;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 自定义 账号登录身份认证组件
        auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
        // 自定义 短信登录身份认证组件
        auth.authenticationProvider(new SmsCodeAuthenticationProvider());
    }


    /**
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        项目中用到iframe嵌入网页，然后用到springsecurity就被拦截了 浏览器报错  x-frame-options deny
//        原因是因为springSecurty使用X-Frame-Options防止网页被Frame
        http.headers().frameOptions().disable();
        // 禁用 csrf(Cross-site request forgery)跨站请求伪造, 由于使用的是JWT，我们这里不需要csrf
        //https://blog.csdn.net/yjclsx/article/details/80349906
        //处理来自浏览器的请求需要是CSRF保护，如果后台服务是提供API调用那么可能就要禁用CSRF保护
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // 跨域预检请求
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                // 登录URL
                .antMatchers(
                        "/mock/**",
                        "/auth/mobile/code",
                        "/auth/user/register",
                        "/auth/captcha",
                        "/resume.html",
                        "/jexcel.html",
                        "/sys/common/**",
                        "/ureport/**",
                        "/pdf/**",
                        "/generic/**",
                        "/actuator/**",
                        "/easypoi/**"
                        ).permitAll()
                //排号大厅
                .antMatchers(
                        "/screen/**",
                        "/js/**",
                        "/assets/**",
                        "/img/**",
                        "/favicon.ico",
                        //文档模板
                        "/freemarker/**",
                        "/xdoc/**",
                        "/qrCode/**",
                        "/sys/log/view",
                        "/connect/**",
                        // swagger
                        "/swagger**/**",
                        "/webjars/**",
                        "/v2/**",
                        "/druid/**",
                        "/doc.html",
                        "/pay.html"
                        ).permitAll()
                //微信 支付宝回调
                .antMatchers(
                        "/aliPay/callback",
                        "/weChatPay/callback",
                        "/weChatPay/refundCallBack",
                        "/fmsPayOrder/payOrder",
                        "/qrCodePay/**"
                        ).permitAll()
                // 其他所有请求需要身份认证
                .anyRequest().authenticated();
        //各类错误异常处理 以下针对于访问资源路径 认证异常捕获 和 无权限处理
        http.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint).accessDeniedHandler(myAccessDeniedHandler);
        // token 校验
        http.addFilterBefore(new JwtTokenAuthenticationFilter(),AbstractPreAuthenticatedProcessingFilter.class);
        // 开启短信登录认证过滤器
        http.addFilterBefore(new SmsCodeLoginFilter(authenticationManager(),myAuthenticationSuccessHandler,myAuthenticationFailureHandler,applicationEventPublisher),UsernamePasswordAuthenticationFilter.class);
        // 开启账号登录认证流程过滤器
        http.addFilterBefore(new JwtLoginFilter(authenticationManager(),myAuthenticationSuccessHandler,myAuthenticationFailureHandler,applicationEventPublisher), UsernamePasswordAuthenticationFilter.class);
        // 退出登录处理器 清除redis 中token GET请求
        http.logout().logoutUrl("/logout").logoutSuccessHandler(myLogoutSuccessHandler);
        // token 不保存session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    }

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

}
